package com.geor.gcv.core.mat;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.nutz.lang.Lang;
import org.opencv.core.Mat;
import org.slf4j.Logger;

import com.geor.common.log.Loggers;
import com.geor.gcv.core.model.Area;

/**
 * 滴水法字符单元切割器实现
 * 
 * @author geor.yuli
 * 
 */
public class CellDripCropperImpl implements CellChipper {

	static Logger log = Loggers.get();

	/** 最大循环次数防止死循环 */
	static int MAX_LOOP = 100;

	/** 最大寻找宽度 */
	int MAX_WIDTH = 12;

	@Override
	public void setParams(Properties properties) {
		MAX_WIDTH = Integer.parseInt((String) properties.get("MAX_WIDTH"));
	}

	public List<Area> execute(Mat mat) throws Exception {
		log.debug(String.format("input mat: {rows: %d, cols: %d}", mat.rows(), mat.cols()));
		List<Area> areas = new ArrayList<Area>(30);
		Mat canvas = mat.clone();

		// 去除顶部悬浮干扰
		int topY = findPeakTop(canvas);

		// 切线偏移量
		int offset = 0;
		// 切割线坐标
		int peakLeft = 0;
		int peakRight = 0;
		for (int r = 0; r < MAX_LOOP; r++) {
			if (offset >= canvas.cols() - 1)
				break;

			// 向右寻找下一个峰顶
			int[] peakPoint = findNextPeak(canvas, topY, offset, offset + MAX_WIDTH);
			log.trace(String.format("peak x: %d, y: %d", peakPoint[0], peakPoint[1]));
			// 滴水分割获取左侧切线坐标
			peakLeft = drip(mat, peakPoint[0], peakPoint[1] - 1, false);
			// 左侧分割线不能小于前一字符的右侧分割线
			if (peakLeft < peakRight)
				peakLeft = peakRight;
			// 滴水分割获取右侧切线坐标
			peakRight = drip(mat, peakPoint[0], peakPoint[1] - 1, true);

			// 寻找左侧的次峰(查找是否会因腐蚀而丢失区域)
			int lastRight = 0;
			if (!Lang.isEmpty(areas)) {
				Area lastArea = areas.get(areas.size() - 1);
				lastRight = lastArea.getX() + lastArea.getWidth();
			}
			int[] hillPoint = findNextPeak(mat, topY, lastRight, peakLeft);
			// 找到次峰并对次峰进行一次滴水确定区域
			if (hillPoint[0] != lastRight) {
				log.trace("hillpoint:" + hillPoint[0], hillPoint[1]);
				// 左侧滴水
				int hillLeft = drip(mat, hillPoint[0], hillPoint[1], false);
				log.trace("hillLeft:" + hillLeft);
				// 右侧滴水
				int hillRight = drip(mat, hillPoint[0], hillPoint[1], true);
				log.trace("hillRight:" + hillRight);

				// 次峰的区域与左侧(如果有)切分区域、右侧切分区域的宽度作比较
				int prevWidth = 0;
				int nextWidth = peakRight - peakLeft;
				if (!Lang.isEmpty(areas)) {
					Area lastArea = areas.get(areas.size() - 1);
					prevWidth = lastArea.getWidth();
				}
				// 左右区域距离
				int distanceLeft = Math.abs(hillLeft - lastRight) + 1;
				int distanceRight = Math.abs(hillRight - peakLeft) + 1;
				// 左右区域权值
				int leftValue = prevWidth * prevWidth * distanceLeft;
				int rightValue = nextWidth * nextWidth * distanceRight;

				log.debug(String.format("left: %d, right: %d", leftValue, rightValue));

				// 右侧合并
				if (leftValue >= rightValue) {
					peakLeft = hillLeft;
				}
				// 左侧合并
				else {
					if (!Lang.isEmpty(areas)) {
						Area lastArea = areas.get(areas.size() - 1);
						lastArea.setWidth(hillRight - lastArea.getX());
					}
				}
			}
			// 底部位置
			int bottom = findBottom(canvas, peakLeft, peakRight);

			SharpArea area = new SharpArea(peakLeft, peakPoint[1], peakRight - peakLeft, bottom - peakPoint[1]);
			areas.add(area);
			offset = peakRight;
		}

		depart(areas);
		return filterAreas(canvas, areas);
	}

	// 滴水式获取分割线列坐标
	private int drip(Mat mat, int x, int y, boolean turnRight) {
		// 保留的顶部高度
		int KEEP_TOP = 2;
		// 保留的底部高度
		int KEEP_BOT = mat.rows() / 4;
		// 最少步数
		int MIN_STEP = 10;
		int thisX = x;
		int thisY = y;
		for (int r = 0; r < MAX_LOOP; r++) {
			// 边界
			if (thisX == mat.cols() - 1 || thisY == mat.rows() - 1 - KEEP_BOT) {
				break;
			}
			double[] matD = mat.get(thisY + 1, thisX);
			// 向下试探遇到障碍
			if (null != matD && matD[0] == 255d) {
				// 向左还是向右
				thisX = (turnRight) ? thisX + 1 : thisX - 1;
				// 有障碍则是死路退出, 过滤顶部毛刺的影响
				if (r > MIN_STEP && Math.abs(thisY - y) > KEEP_TOP && mat.get(thisY, thisX)[0] == 255d) {
					// 退出前回撤
					thisX = (turnRight) ? thisX - 1 : thisX + 1;
					break;
				}
			}
			// 如果没有障碍则继续下探
			else {
				thisY++;
			}
		}
		return thisX;
	}

	/**
	 * 寻找下一个峰顶
	 * 
	 * @param mat
	 * @param topY
	 *            Y起点
	 * @param left
	 *            左侧范围X
	 * @param right
	 *            右侧范围X
	 * @return
	 */
	private int[] findNextPeak(Mat mat, int topY, int left, int right) {
		int[] peakPoint = new int[2];
		// X
		peakPoint[0] = left;
		// Y
		peakPoint[1] = 0;

		int findWidth = Math.min(right, mat.cols());
		List<Integer> topPoints = new ArrayList<Integer>(right - left + 1);
		// 上一个峰顶X坐标
		int last = 0;
		for (int i = topY; i < mat.rows(); i++) {
			for (int j = left; j < findWidth; j++) {
				if (mat.get(i, j)[0] == 255) {
					if (log.isTraceEnabled()) {
						log.trace(String.format("findTop -- this: %d, left: %d", j, last));
					}
					if (j > last && (last == 0 || j - last == 1)) {
						last = j;
						topPoints.add(j);
					}
				}
			}
			// 找到了就不用继续
			if (!Lang.isEmpty(topPoints)) {
				// 峰顶Y坐标即字符Y坐标
				peakPoint[1] = i;
				break;
			}
		}
		// 计算并返回峰顶均值
		int total = 0;
		for (Integer topPoint : topPoints) {
			total += topPoint.intValue();
		}
		if (Lang.isEmpty(topPoints)) {
			return peakPoint;
		}
		int size = topPoints.size();
		log.trace(String.format("total: %d, size: %d", total, size));
		peakPoint[0] = total / size;
		topPoints.clear();
		return peakPoint;
	}

	/**
	 * 找到Mat峰顶Y坐标, 去除顶上的点干扰
	 * 
	 * @param mat
	 * @return
	 */
	private int findPeakTop(Mat mat) {
		int[] horizons = new int[mat.rows()];
		for (int i = 0; i < mat.rows(); i++) {
			int total = 0;
			for (int j = 0; j < mat.cols(); j++) {
				if (mat.get(i, j)[0] == 255) {
					total++;
				}
			}
			horizons[i] = total;
		}
		int index = 0;
		int height = 0;
		for (int i = 0; i < horizons.length; i++) {
			if (horizons[i] > 0) {
				height++;
				index = i;
			} else {
				height = 0;
			}
			if (height >= Math.min(mat.rows(), 5)) {
				return index + 1 - height;
			}
		}
		return 0;
	}

	/**
	 * 找到一个区间的底部
	 * 
	 * @param mat
	 * @param left
	 * @param right
	 * @return
	 */
	private int findBottom(Mat mat, int left, int right) {
		for (int i = mat.rows() - 1; i > 0; i--) {
			for (int j = left; j < right; j++) {
				if (mat.get(i, j)[0] == 255) {
					return i;
				}
			}
		}
		return mat.rows() - 1;
	}

	/**
	 * 切分粘连区域
	 * 
	 * @param areas
	 * @return
	 */
	private void depart(List<Area> areas) {

		// 计算已有区域的平均宽度
		int totalWidth = 0;
		int counter = 0;
		for (Area area : areas) {
			if (((SharpArea) area).sharp < 1) {
				counter++;
				totalWidth += area.getWidth();
			}
		}
		// 正常区域的平均宽度
		float areaAvgWidth = (float) totalWidth / (float) counter;
		log.trace(String.format("total width: %d, avg width: %f", totalWidth, areaAvgWidth));

		// 忽略非正常区域
		if (areaAvgWidth < 3) {
			return;
		}

		// 超宽区域处理
		for (int i = 0; i < areas.size(); i++) {
			SharpArea area = (SharpArea) areas.get(i);
			if (area.sharp >= 1) {
				List<SharpArea> children = new ArrayList<SharpArea>();
				int childnum = Math.round((float) area.getWidth() / areaAvgWidth);
				if (childnum == 0)
					continue;
				int childWidth = area.getWidth() / childnum;
				log.trace(String.format("[%d] {num: %d, width: %d}", i, childnum, childWidth));
				int offset = area.getX();
				for (int j = 0; j < childnum; j++) {
					SharpArea childArea = new SharpArea(offset, area.getY(), childWidth, area.getHeight());
					log.trace(String.format("child: {x: %d, width: %d}", childArea.getX(), childArea.getWidth()));
					offset += childArea.getWidth() + 1;
					children.add(childArea);
				}
				areas.remove(i);
				areas.addAll(i, children);
				children.clear();
			}
		}
	}

	/**
	 * 不合理区域过滤
	 * 
	 * @param areas
	 * @return
	 */
	private List<Area> filterAreas(Mat mat, List<Area> areas) {

		Iterator<Area> iterator = areas.iterator();
		while (iterator.hasNext()) {
			Area area = iterator.next();
			// 去除面积为0的区域
			if (area.getWidth() * area.getHeight() == 0) {
				iterator.remove();
			}
		}

		// 去除波动较大的区域, 计算差值 = Y起点偏差 * 高度偏差 * 边距偏差
		int[] offsets = new int[areas.size()];
		int begin = 0;
		for (int i = 1; i < areas.size(); i++) {

			// Area area = areas.get(i);
			// int block = 0;
			// for (int r = area.getY(); r < area.getY() + area.getHeight();
			// r++) {
			// for (int c = area.getX(); c < area.getX() + area.getWidth(); c++)
			// {
			// double[] point = mat.get(r, c);
			// if (null != point && point[0] == 255d) {
			// block++;
			// }
			// }
			// }
			// log.debug(String.format("[%d] point: %f", i, (float)block /
			// (float)(area.getWidth() * area.getHeight())));

			// log.debug(String.format("[%d]: {x: %d, y: %d, w: %d, h: %d}", i,
			// areas.get(i).getX(), areas.get(i).getY(),
			// areas.get(i).getWidth(), areas.get(i).getHeight()));
			int offsetT = Math.abs(areas.get(i).getY() - areas.get(i - 1).getY()) + 1;
			int offsetB = Math.abs(areas.get(i).getY() + areas.get(i).getHeight() - areas.get(i - 1).getY()
					- areas.get(i - 1).getHeight()) + 1;
			int offsetD = Math.abs(areas.get(i).getX() - (areas.get(i - 1).getX() + areas.get(i - 1).getWidth())) + 1;
			offsets[i] = offsetT * offsetB * offsetD;
			// log.debug(String.format("[%d - %d]: %d", i - 1, i, offsets[i]));
			if (offsets[i] > 100) {
				begin = i;
			}
		}
		// return areas.subList(0, begin);
		return areas.subList(begin, areas.size());
	}

	/**
	 * 形态区域
	 */
	class SharpArea extends Area {
		/** 宽高比例 */
		float sharp;

		public SharpArea(int x, int y, int width, int height) {
			super(x, y, width, height);
			sharp = (float) width / (float) height;
		}
	}

}
